package com.edev.support.dao.impl.utils;

import com.edev.support.dsl.DomainObject;
import com.edev.support.dsl.DomainObjectFactory;
import com.edev.support.entity.Entity;
import com.edev.support.utils.BeanUtils;

import lombok.NonNull;

import java.io.Serializable;
import java.util.*;

/**
 * DaoEntity对象的构建工具类
 * <p>
 * 核心功能：
 * 1. 将领域实体及其元数据转换为数据库操作所需的参数
 * 2. 通过这些参数构建DaoEntity对象，用于数据访问层的操作
 */
public class DaoEntityBuilder {
    public static final String KEY = "key";
    public static final String VALUE = "value";
    private DaoEntityBuilder() {}
    /**
     * 自动解析领域对象中的数据，并查找该领域对象的元数据，构建DaoEntity对象。
     * 该DaoEntity对象携带了表名、字段及其相关数据的映射，用于数据访问层的操作。
     * @param entity 领域实体对象
     * @return 包含表名和字段映射的DaoEntity
     */
    public static DaoEntity build(Entity<?> entity) {
        DomainObject dObj = DomainObjectFactory.getDomainObject(entity.getClass());
        return build(entity, dObj);
    }

    /**
     * 自动解析领域对象中的数据，并查找该领域对象的元数据，构建DaoEntity对象。
     * 该DaoEntity对象携带了表名、字段及其相关数据的映射，用于数据访问层的操作。
     * 注意：该方法用于当携带数据的领域对象与要写数据的领域对象及其元数据分离时使用。
     * @param entity 携带数据的领域对象
     * @param dObj 真正要写数据的领域对象的元数据
     * @return 包含表名和字段映射的DaoEntity
     */
    public static DaoEntity build(Entity<?> entity, DomainObject dObj) {
        DaoEntity daoEntity = new DaoEntity();
        daoEntity.setTableName(dObj.getTable());
        daoEntity.setProperties(entity, dObj.getProperties());
        return daoEntity;
    }

    /**
     * 构建daoEntity对象进行批量操作，如批量查找和删除。
     * 该方法最终会在pkMap中添加一个主键集合，如：[{key:"id", value:[id0,id1,id2]}]
     * @param ids 主键集合
     * @param clazz 实体类的Class对象
     * @return 包含主键集合的DaoEntity
     */
    public static <S extends Serializable, T extends Entity<S>>
        DaoEntity buildForList(Collection<S> ids, @NonNull Class<T> clazz) {
        if(ids==null||ids.isEmpty()) return null;
        T template = BeanUtils.createBean(clazz);

        List<DaoEntity> listOfDaoEntity = getListOfDaoEntities(ids, template);
        Map<Object, List<Object>> mapOfValues = getMapOfValues(listOfDaoEntity);
        return buildDaoEntity(mapOfValues, template);
    }

    /**
     * get list of DaoEntity for each of id
     * @param ids collection of ids
     * @param template the template of the class
     * @param <E> Entity
     * @param <S> id
     * @return list of DaoEntities
     */
    private static <E extends Entity<S>, S extends Serializable>
    List<DaoEntity> getListOfDaoEntities(Collection<S> ids, E template) {
        List<DaoEntity> listOfDaoEntity = new ArrayList<>();
        ids.forEach(id -> {
            @SuppressWarnings("unchecked")
            E entity = (E) template.clone();
            entity.setId(id);
            DaoEntity daoEntity = build(entity);
            listOfDaoEntity.add(daoEntity);
        });
        return listOfDaoEntity;
    }

    /**
     * get map like this: { pk0:listOfValues0, pk1:listOfValues1 }
     * @param listOfDaoEntity list of DaoEntity
     * @return map of values
     */
    private static Map<Object, List<Object>> getMapOfValues(List<DaoEntity> listOfDaoEntity) {
        Map<Object, List<Object>> mapOfValues = new HashMap<>();
        listOfDaoEntity.forEach(daoEntity ->
                daoEntity.getPkMap().forEach(map -> {
                    Object key = map.get(KEY);
                    Object value = map.get(VALUE);
                    mapOfValues.computeIfAbsent(key, k -> new ArrayList<>());
                    mapOfValues.get(key).add(value);
                }));
        return mapOfValues;
    }

    /**
     * build the final daoEntity with the colMap like: { pk0:listOfValues0, pk1:listOfValues1 }
     * @param mapOfValues map of values
     * @param template the template of the class
     * @return the final daoEntity
     */
    private static DaoEntity buildDaoEntity(Map<Object, List<Object>> mapOfValues, Entity<?> template) {
        DaoEntity daoEntity = build(template);
        List<Map<Object, Object>> colMap = new ArrayList<>();
        for(Map.Entry<Object, List<Object>> entry: mapOfValues.entrySet()) {
            Map<Object, Object> map = new HashMap<>();
            map.put(KEY, entry.getKey());
            map.put(VALUE, entry.getValue());
            colMap.add(map);
        }
        daoEntity.setConditionMap(colMap);
        return daoEntity;
    }
}
